package mmpathgen.callgraphanalysis;

import java.io.File;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

import mmpathgen.util.ClassLoader;
import soot.Local;
import soot.PointsToSet;
import soot.Scene;
import soot.SootClass;
import soot.SootField;
import soot.SootMethod;
import soot.Unit;
import soot.Value;
import soot.ValueBox;
import soot.jimple.JimpleBody;
import soot.jimple.Stmt;
import soot.jimple.spark.SparkTransformer;
import soot.jimple.toolkits.callgraph.CallGraph;
import soot.options.Options;
import soot.tagkit.LineNumberTag;
import soot.tagkit.Tag;

public class CallGraphGenBySpark {

	public static void main(String[] args) {

		String[] pkgList = { "wei.he.stkm", };
		String[][] clsList = { { "Bike", "BikeShop", "Fiero", "Pulsar",
				"Splendor" }, };
		String mainClassName = "wei.he.stkm.Main";

		CallGraph cg = cgGen(pkgList, clsList, mainClassName);

		UnfilteredCallGraph ucg = CallGraphAnalysis.getUnfilteredCallGraph(cg);
		
		CallGraphAnalysis.printCallGraphAsDot(ucg, mainClassName, "Spark", "Unfilted");

	}

	public static CallGraph cgGen(File path, String mainClassName) {
		Options.v().set_whole_program(true);
		Options.v().set_keep_line_number(true);
		// Options.v().setPhaseOption("cg", "verbose:true");
		Options.v().setPhaseOption("cg", "safe-forname:true");
		Options.v().setPhaseOption("cg", "safe-newinstance:true");

		ClassLoader.loadClass(path, mainClassName);

		setSparkPointsToAnalysis();

		CallGraph cg = Scene.v().getCallGraph();

		return cg;
	}

	public static CallGraph cgGen(String[] pkgList, String[][] clsList,
			String mainClassName) {

		Options.v().set_whole_program(true);
		Options.v().set_keep_line_number(true);
		// Options.v().setPhaseOption("cg", "verbose:true");
		Options.v().setPhaseOption("cg", "safe-forname:true");
		Options.v().setPhaseOption("cg", "safe-newinstance:true");

		ClassLoader.loadClass(pkgList, clsList, mainClassName);

		setSparkPointsToAnalysis();

		CallGraph cg = Scene.v().getCallGraph();

		return cg;
	}

	static void setSparkPointsToAnalysis() {
//		System.out.println("[spark] Starting analysis ...");

		HashMap<String, String> opt = new HashMap<String, String>();
		opt.put("enabled", "true");
		opt.put("verbose", "false");
		opt.put("ignore-types", "false");
		opt.put("force-gc", "false");
		opt.put("pre-jimplify", "false");
		opt.put("vta", "false");
		opt.put("rta", "false");
		opt.put("field-based", "false");
		opt.put("types-for-sites", "false");
		opt.put("merge-stringbuffer", "true");
		opt.put("string-constants", "false");
		opt.put("simulate-natives", "true");
		opt.put("simple-edges-bidirectional", "false");
		opt.put("on-fly-cg", "false");
		opt.put("simplify-offline", "false");
		opt.put("simplify-sccs", "false");
		opt.put("ignore-types-for-sccs", "false");
		opt.put("propagator", "worklist");
		opt.put("set-impl", "double");
		opt.put("double-set-old", "hybrid");
		opt.put("double-set-new", "hybrid");
		opt.put("dump-html", "false");
		opt.put("dump-pag", "false");
		opt.put("dump-solution", "false");
		opt.put("topo-sort", "false");
		opt.put("dump-types", "true");
		opt.put("class-method-var", "true");
		opt.put("dump-answer", "false");
		opt.put("add-tags", "false");
		opt.put("set-mass", "false");

		SparkTransformer.v().transform("", opt);

//		System.out.println("[spark] Done!");
	}

	private static SootClass loadClass(String name, boolean main) {
		SootClass c = Scene.v().loadClassAndSupport(name);
		c.setApplicationClass();
		if (main)
			Scene.v().setMainClass(c);
		return c;
	}

	private static int getLineNumber(Stmt s) {
		Iterator<Tag> ti = s.getTags().iterator();
		while (ti.hasNext()) {
			Object o = ti.next();
			if (o instanceof LineNumberTag)
				return Integer.parseInt(o.toString());
		}
		return -1;
	}

	private static SootField getField(String classname, String fieldname) {
		Collection<SootClass> app = Scene.v().getApplicationClasses();
		Iterator<SootClass> ci = app.iterator();
		while (ci.hasNext()) {
			SootClass sc = ci.next();
			if (sc.getName().equals(classname))
				return sc.getFieldByName(fieldname);
		}
		throw new RuntimeException("Field " + fieldname
				+ " was not found in class " + classname);
	}

	private static Map/* <Integer,Local> */<Integer, Value> getLocals(
			SootClass sc, String methodname, String typename) {
		Map<Integer, Value> res = new HashMap<Integer, Value>();
		Iterator<SootMethod> mi = sc.getMethods().iterator();
		while (mi.hasNext()) {
			SootMethod sm = mi.next();
			System.err.println(sm.getName());
			if (true && sm.getName().equals(methodname) && sm.isConcrete()) {
				JimpleBody jb = (JimpleBody) sm.retrieveActiveBody();
				Iterator<Unit> ui = jb.getUnits().iterator();
				while (ui.hasNext()) {
					Stmt s = (Stmt) ui.next();
					int line = getLineNumber(s);
					// find definitions
					Iterator<ValueBox> bi = s.getDefBoxes().iterator();
					while (bi.hasNext()) {
						Object o = bi.next();
						if (o instanceof ValueBox) {
							Value v = ((ValueBox) o).getValue();
							if (v.getType().toString().equals(typename)
									&& v instanceof Local)
								res.put(new Integer(line), v);
						}
					}
				}
			}
		}

		return res;
	}

	private static void printLocalIntersects(
			Map/* <Integer,Local> */<Integer, Value> ls) {
		soot.PointsToAnalysis pta = Scene.v().getPointsToAnalysis();
		Iterator<Entry<Integer, Value>> i1 = ls.entrySet().iterator();
		while (i1.hasNext()) {
			Map.Entry<Integer, Value> e1 = i1.next();
			int p1 = ((Integer) e1.getKey()).intValue();
			Local l1 = (Local) e1.getValue();
			PointsToSet r1 = pta.reachingObjects(l1);
			Iterator<Entry<Integer, Value>> i2 = ls.entrySet().iterator();
			while (i2.hasNext()) {
				Map.Entry<Integer, Value> e2 = (Map.Entry<Integer, Value>) i2
						.next();
				int p2 = ((Integer) e2.getKey()).intValue();
				Local l2 = (Local) e2.getValue();
				PointsToSet r2 = pta.reachingObjects(l2);
				if (p1 <= p2)
					System.out.println("[" + p1 + "," + p2
							+ "]\t Container intersect? "
							+ r1.hasNonEmptyIntersection(r2));
			}
		}
	}

	private static void printFieldIntersects(
			Map/* <Integer,Local> */<Integer, Value> ls, SootField f) {
		soot.PointsToAnalysis pta = Scene.v().getPointsToAnalysis();
		Iterator<Entry<Integer, Value>> i1 = ls.entrySet().iterator();
		while (i1.hasNext()) {
			Map.Entry<Integer, Value> e1 = i1.next();
			int p1 = ((Integer) e1.getKey()).intValue();
			Local l1 = (Local) e1.getValue();
			PointsToSet r1 = pta.reachingObjects(l1, f);
			Iterator<Entry<Integer, Value>> i2 = ls.entrySet().iterator();
			while (i2.hasNext()) {
				Map.Entry<Integer, Value> e2 = (Map.Entry<Integer, Value>) i2
						.next();
				int p2 = ((Integer) e2.getKey()).intValue();
				Local l2 = (Local) e2.getValue();
				PointsToSet r2 = pta.reachingObjects(l2, f);
				if (p1 <= p2)
					System.out.println("[" + p1 + "," + p2
							+ "]\t Container.item intersect? "
							+ r1.hasNonEmptyIntersection(r2));
			}
		}
	}

}
